import hashlib
import math
import os
import shutil
import zipfile
import xml.etree.ElementTree as ET
from PyQt5.QtCore import pyqtSignal, QThread
from IflytekModelTool import getIflyModelDict, setIflyModelDict
from SpiFlashUpdateModelTool import getFlashUpdateModelVersion, setSpiFlashUpdateModelDict, getSpiFlashUpdateModelDict


def ensure_folder_exists(path):
    """
    确保路径存在，如果不存在则创建目录。
    """
    if not os.path.exists(path):
        os.makedirs(path)  # 递归创建目录
        # print(f"已创建目录：{path}")
    else:
        # print(f"目录已存在：{path}")
        pass


def check_filesys():
    """
    检查软件必要的目录结构
    """
    ensure_folder_exists('./exported')
    ensure_folder_exists('./generate/kedaxunfei')
    ensure_folder_exists('./generate/mcu')
    ensure_folder_exists('./project/kedaxunfei')
    ensure_folder_exists('./project/mcu')
    ensure_folder_exists('./res')
    ensure_folder_exists('./log')
    ensure_folder_exists('./convert')
    ensure_folder_exists('./AudioProcess')


def get_all_folder_names(path):
    """
    列出 path 路径下的所有内容
    :param path:
    :return:
    """
    try:
        # 列出 path 路径下的所有内容
        all_items = os.listdir(path)

        # 过滤出文件夹，仅保留 isdir 的路径
        folders = [item for item in all_items if os.path.isdir(os.path.join(path, item))]
        return folders
    except FileNotFoundError:
        # print(f"路径 '{path}' 不存在！")
        return []


def generate_unique_folder_name(base_folder, folder_name):
    """
    根据指定的文件夹名称，生成唯一的文件夹名称。
    如果文件夹已存在，则在名称后添加后缀 (-1, -2, -3...)。

    :param base_folder: 文件夹的父目录路径
    :param folder_name: 原始文件夹名称
    :return: 一个唯一的文件夹名称
    """
    unique_name = folder_name
    counter = 1

    # 判断文件夹是否已存在，若存在则递增后缀
    while os.path.exists(os.path.join(base_folder, unique_name)):
        unique_name = f"{folder_name}-{counter}"
        counter += 1

    return unique_name


def add_suffix_if_exists(file_path):
    # 检测该文件是否存在，如果存在，就在文件名后添加 "(1)"
    # 检查文件是否存在
    counter = 1
    unique_Path = file_path
    while os.path.exists(unique_Path):
        # 分割出目录、文件名和扩展名
        directory, file_name = os.path.split(file_path)
        file_base, file_ext = os.path.splitext(file_name)

        # 新文件名加上 "(1)"
        unique_Name = f"{file_base}-{counter}{file_ext}"
        unique_Path = os.path.join(directory, unique_Name)
        counter += 1

    return unique_Path


def delete_folder(folder_path):
    """
    删除指定路径的文件夹。
    如果文件夹是空的，使用 os.rmdir()，否则使用 shutil.rmtree()。
    """
    if not os.path.exists(folder_path):
        # print(f"文件夹 {folder_path} 不存在！")
        return -1

    try:
        # 检查文件夹是否为空
        if not os.listdir(folder_path):  # 空文件夹
            os.rmdir(folder_path)
            # print(f"已成功删除空文件夹: {folder_path}")
        else:  # 非空文件夹
            shutil.rmtree(folder_path)
            # print(f"已成功删除文件夹及其所有内容: {folder_path}")
        return 0
    except PermissionError:
        # print(f"没有权限删除文件夹: {folder_path}")
        return -2
    except Exception as e:
        # print(f"删除文件夹时出现错误: {e}")
        return -3


def replaceCRLFtoLF(file_path: str):
    """
        把文件中的'\r\n'都替换为'\n'
        :param file_path:原文件路径
        :return:
    """
    content = ""

    with open(file_path, 'r') as input_file:
        content = input_file.read()

    new_content = content.replace('\r\n', '\n')

    with open(file_path, 'w') as output_file:
        output_file.write(new_content)
        # 在临时文件结尾新增一个0x0a
        output_file.write('\n')


def calculate_md5(file_path):
    # 创建一个 MD5 哈希对象
    md5_hash = hashlib.md5()

    # 以二进制模式读取文件
    with open(file_path, "rb") as f:
        # 逐块读取文件内容并更新哈希对象
        for chunk in iter(lambda: f.read(4096), b""):
            md5_hash.update(chunk)

    # 返回十六进制格式的哈希值
    return md5_hash.hexdigest().upper()


class UnzipThread(QThread):
    """
    一个线程类，用于执行解压缩操作。
    """
    progress_signal = pyqtSignal(int)    # 用于发送进度信号（0~100）
    status_signal = pyqtSignal(int)     # 用于发送状态信号

    def __init__(self, zip_file, output_folder):
        super().__init__()
        self.zip_file = zip_file        # 压缩文件路径
        self.output_folder = output_folder  # 解压目标路径

    def run(self):
        try:
            # 打开压缩文件
            with zipfile.ZipFile(self.zip_file, 'r') as zip_ref:
                file_list = zip_ref.namelist()  # 获取所有文件名
                total_files = len(file_list)   # 总文件数量

                # 遍历每个文件，逐一解压
                for i, file in enumerate(file_list):
                    zip_ref.extract(file, self.output_folder)  # 解压文件到目标路径

                    # 计算进度百分比
                    progress = int((i + 1) / total_files * 100)
                    self.progress_signal.emit(progress)  # 发送进度信号

                self.status_signal.emit(0)  # 解压完成时发送状态信号
        except Exception as e:
            self.status_signal.emit(-1)  # 如果出现错误，发送错误信息


class ZipThread_iflytek(QThread):
    """
    一个线程类，用于执行解压缩操作。
    """
    progress_signal = pyqtSignal(int)    # 用于发送进度信号（0~100）
    status_signal = pyqtSignal(int)     # 用于发送状态信号

    def __init__(self, projectName: str):
        super().__init__()
        self.projectName = projectName

    def run(self):
        base_path = './project/kedaxunfei/'
        folder_path = os.path.join(base_path, self.projectName)

        zip_file_path = os.path.join('./generate/kedaxunfei/', f"{self.projectName}.zip")

        folders_to_zip = ['ica', 'res', 'iflytek_aec']
        try:
            # 创建 ZIP 文件
            with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                total_files = 0
                files_processed = 0

                # 计算总文件数
                for folder in folders_to_zip:
                    folder_to_zip_path = os.path.join(folder_path, folder)
                    if os.path.exists(folder_to_zip_path):
                        for _, _, files in os.walk(folder_to_zip_path):
                            total_files += len(files)

                # 更新进度条
                for folder in folders_to_zip:
                    folder_to_zip_path = os.path.join(folder_path, folder)
                    if os.path.exists(folder_to_zip_path):
                        for root, _, files in os.walk(folder_to_zip_path):
                            for file in files:
                                file_path = os.path.join(root, file)
                                zipf.write(file_path, os.path.relpath(file_path, folder_path))
                                files_processed += 1
                                # 发送进度信号
                                self.progress_signal.emit(int(files_processed / total_files * 100))

                # 解压完成时发送状态信号
                self.status_signal.emit(0)
        except Exception as e:
            self.status_signal.emit(-1)  # 如果出现错误，发送错误信息


class ModifyThread_iflytek(QThread):
    """
    一个线程类，用于执行解压缩操作。
    """
    progress_signal = pyqtSignal(int)    # 用于发送进度信号（0~100）
    status_signal = pyqtSignal(int)     # 用于发送状态信号

    def __init__(self, projectName: str):
        super().__init__()
        self.projectName = projectName

    def run(self):
        try:
            units = getIflyModelDict(self.projectName)
            total = len(units)
            # print(total)
            # print(units)
            for i in range(len(units)):
                unit = units[i]
                # 一系列操作，同步更新unit
                if unit['curFile'] != "":
                    # 判断文件是否存在，不存在跳过
                    if not os.path.exists(unit['curFile']):
                        # print(f"{str(i)}err")
                        continue

                    # 把文件复制过去
                    try:
                        filePath = "./project/kedaxunfei/" + self.projectName + unit['orgFile'][1:]
                        # 复制并替换文件
                        # print(f"{filePath}")
                        shutil.copy(unit['curFile'], filePath)
                        # print(f'文件已成功复制并替换：{destination_file_path}')
                    except Exception as e:
                        # TODO::删除目标文件，重试？
                        # print(f"{filePath} err")
                        # print(f"{unit['curFile']} err")
                        continue

                    # 判断复制成功，就清空
                    unit['curFile'] = ""

                    # 操作后，将模型数据更新
                    units[i] = unit

                    setIflyModelDict(self.projectName, unit['key'], unit)
                self.progress_signal.emit(int((i+1) / total * 100))

            self.status_signal.emit(0)  # 完成时发送状态信号
        except Exception as e:
            self.status_signal.emit(-1)  # 如果出现错误，发送错误信息


class ZipThread_spiFlashUpdate(QThread):
    """
    一个线程类，用于执行解压缩操作。
    """
    progress_signal = pyqtSignal(int)    # 用于发送进度信号（0~100）
    status_signal = pyqtSignal(int)     # 用于发送状态信号

    def __init__(self, projectName: str):
        super().__init__()
        self.projectName = projectName

    def run(self):
        base_path = './project/mcu/'
        folder_path = os.path.join(base_path, self.projectName)

        zip_file_path = os.path.join('./generate/mcu/', f"{self.projectName}.zip")

        folders_to_zip = ['files']
        try:
            # 创建 ZIP 文件
            with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                total_files = 0
                files_processed = 0

                # 计算总文件数
                for folder in folders_to_zip:
                    folder_to_zip_path = os.path.join(folder_path, folder)
                    if os.path.exists(folder_to_zip_path):
                        for _, _, files in os.walk(folder_to_zip_path):
                            total_files += len(files)

                # 更新进度条
                for folder in folders_to_zip:
                    folder_to_zip_path = os.path.join(folder_path, folder)
                    if os.path.exists(folder_to_zip_path):
                        for root, _, files in os.walk(folder_to_zip_path):
                            for file in files:
                                file_path = os.path.join(root, file)
                                zipf.write(file_path, os.path.relpath(file_path, folder_path))
                                files_processed += 1
                                # 发送进度信号
                                self.progress_signal.emit(int(files_processed / total_files * 100))

                # 解压完成时发送状态信号
                self.status_signal.emit(0)
        except Exception as e:
            self.status_signal.emit(-1)  # 如果出现错误，发送错误信息


class ModifyThread_spiFlashUpdate(QThread):
    """
    一个线程类，用于执行解压缩操作。
    """
    progress_signal = pyqtSignal(int)    # 用于发送进度信号（0~100）
    status_signal = pyqtSignal(int)     # 用于发送状态信号

    def __init__(self, projectName: str):
        super().__init__()
        self.projectName = projectName
        # 获取version值
        self.version = getFlashUpdateModelVersion(self.projectName)

    def run(self):
        units = getSpiFlashUpdateModelDict(self.projectName)
        total = len(units)
        cur = 0
        # print(total)
        # print(units)

        # 读取XML文件
        xmlfilePath = "./project/mcu/" + self.projectName + "/files/SpiFlashInfo.xml"
        tree = ET.parse(xmlfilePath)
        root = tree.getroot()

        # 遍历xml-file节点
        for file_node in root.findall('file'):
            # 获取id节点的值
            id_node = file_node.find('id')
            # print(type(id_node))
            # 以XML配置文件为准来找数据模型
            for i in range(total):
                unit = units[i]
                if unit['key'] == id_node.text:
                    if unit['enable'] == '1':
                        if unit['curFile'] == "":   # 没修改
                            # 更新XML信息
                            # version
                            version_node = file_node.find('version')
                            version_node.text = self.version
                        elif os.path.exists(unit['curFile']):   # 判断目标文件是否存在
                            # 判断目标文件大小是否在范围内
                            file_size = os.path.getsize(unit['curFile'])
                            if file_size < int(unit['maxsize']):
                                # 把文件复制过去
                                copyFlag = False
                                filePath = "./project/mcu/" + self.projectName + unit['orgFile'][1:]
                                try:
                                    # 复制并替换文件
                                    # print(f"{filePath}")
                                    # 针对BIN文件需要操作一下
                                    if unit['fmt'] == 'BIN':
                                        # 取原文件前0xa0(160)字节，加到新文件的前面
                                        with open(filePath, 'rb') as src:
                                            prefix_data = src.read(160)
                                        # 读取目标文件的全部内容
                                        with open(unit['curFile'], 'rb') as tgt:
                                            tgt.seek(160)
                                            target_data = tgt.read()
                                        with open(filePath, 'wb') as src:
                                            src.write(prefix_data)
                                            src.write(target_data)
                                    else:
                                        shutil.copy(unit['curFile'], filePath)
                                    # print(f'文件已成功复制并替换：{destination_file_path}')
                                    copyFlag = True
                                except Exception as e:
                                    # TODO::删除目标文件，重试？
                                    print(f"{filePath} err")
                                    self.status_signal.emit(-1)  # 如果出现错误，发送错误信息

                                if copyFlag:
                                    # 更新XML信息
                                    # version
                                    version_node = file_node.find('version')
                                    version_node.text = self.version
                                    # description
                                    description_node = file_node.find('description')
                                    description_node.text = str(math.ceil(file_size / 1024))
                                    # md5
                                    md5_node = file_node.find('md5')
                                    md5_node.text = calculate_md5(filePath)
                                    # real_size
                                    real_size_node = file_node.find('real_size')
                                    real_size_node.text = str(file_size)

                                    # 更新模型文件
                                    # 判断复制成功，就清空
                                    unit['curFile'] = ""
                                    unit['curFileMD5'] = ""
                                    # 操作后，将模型数据更新
                                    units[i] = unit
                                    setSpiFlashUpdateModelDict(self.projectName, unit['key'], unit)

                    cur += 1
                    self.progress_signal.emit(int(cur / total * 100))
                    break

        # 更新XML文件
        # 没有异常就保存xml文件，对xml文件进行操作
        os.remove(xmlfilePath)
        tree.write(xmlfilePath, xml_declaration=True, encoding="utf-8", method="xml")
        replaceCRLFtoLF(xmlfilePath)
        self.status_signal.emit(0)


class upgradeThread(QThread):
    """
        一个线程类，用于执行构建升级U盘操作。
    """
    progress_signal = pyqtSignal(int)  # 用于发送进度信号（0~100）
    status_signal = pyqtSignal(int)  # 用于发送状态信号
    def __init__(self, drive_path: str, curPath_iflytek: str, curPath_spiFlashUpdate: str):
        super().__init__()
        self.drive_path = drive_path
        self.curPath_iflytek = curPath_iflytek
        self.curPath_spiFlashUpdate = curPath_spiFlashUpdate

    def run(self):
        # 检测U盘是否有升级包，有的话跳过这个步骤
        total_files = sum([len(files) for r, d, files in os.walk('./res/UpgradePack')]) + 2
        copied_files = 0

        if not os.path.exists(self.drive_path+"UpgradePack") or len(os.listdir(self.drive_path+"UpgradePack")) != 6:
            try:
                for root, dirs, files in os.walk('./res/UpgradePack'):
                    relative_path = os.path.relpath(root, './res/UpgradePack')
                    dest_dir = os.path.join(self.drive_path+"UpgradePack", relative_path)
                    os.makedirs(dest_dir, exist_ok=True)

                    for file in files:
                        src_file = os.path.join(root, file)
                        dest_file = os.path.join(dest_dir, file)
                        shutil.copy2(src_file, dest_file)
                        copied_files += 1
                        progress_percent = int((copied_files / total_files) * 100)
                        self.progress_signal.emit(progress_percent)

                # self.status_signal.emit(0)
            except Exception as e:
                # print(f"Error during copy: {e}")
                self.status_signal.emit(-1)
        else:
            copied_files = total_files - 2
            progress_percent = int((copied_files / total_files) * 100)
            self.progress_signal.emit(progress_percent)

        # 讯飞语音资源
        try:
            if self.curPath_iflytek != "":
                shutil.copy2(self.curPath_iflytek, self.drive_path+"UpgradePack/iflytek/iflytek.zip")
            else:
                shutil.copy2('./res/iflytek.zip', self.drive_path+"UpgradePack/iflytek/")

            md5 = calculate_md5(self.drive_path+"UpgradePack/iflytek/iflytek.zip")
            with open(self.drive_path+"UpgradePack/iflytek/md5.txt", 'w') as src:
                src.write(md5)

            copied_files += 1
            progress_percent = int((copied_files / total_files) * 100)
            self.progress_signal.emit(progress_percent)
        except Exception as e:
            # print(f"Error during copy: {e}")
            self.status_signal.emit(-1)

        # MCU音频资源
        try:
            if self.curPath_spiFlashUpdate != "":
                shutil.copy2(self.curPath_spiFlashUpdate, self.drive_path+"UpgradePack/flashUpdate/SpiFlashUpdate.zip")
            else:
                shutil.copy2('./res/SpiFlashUpdate.zip', self.drive_path+"UpgradePack/flashUpdate/")

            md5 = calculate_md5(self.drive_path+"UpgradePack/flashUpdate/SpiFlashUpdate.zip")
            with open(self.drive_path+"UpgradePack/flashUpdate/md5.txt", 'w') as src:
                src.write(md5)

            copied_files += 1
            progress_percent = int((copied_files / total_files) * 100)
            self.progress_signal.emit(progress_percent)
        except Exception as e:
            # print(f"Error during copy: {e}")
            self.status_signal.emit(-1)

        self.status_signal.emit(0)
